
package de.stoneageracer.core;

import com.jme3.app.SimpleApplication;
import com.jme3.asset.AssetManager;
import com.jme3.bullet.BulletAppState;
import com.jme3.bullet.PhysicsSpace;
import com.jme3.bullet.control.RigidBodyControl;
import com.jme3.util.SkyFactory;
import com.jme3.bounding.BoundingBox;
import com.jme3.bullet.collision.shapes.CollisionShape;
import com.jme3.bullet.control.VehicleControl;
import com.jme3.bullet.util.CollisionShapeFactory;
import com.jme3.effect.ParticleEmitter;
import com.jme3.effect.ParticleMesh;
import com.jme3.font.BitmapText;
import com.jme3.input.ChaseCamera;
import com.jme3.input.KeyInput;
import com.jme3.input.controls.ActionListener;
import com.jme3.input.controls.KeyTrigger;
import com.jme3.light.AmbientLight;
import com.jme3.light.DirectionalLight;
import com.jme3.material.Material;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.math.Matrix3f;
import com.jme3.math.Vector3f;
import com.jme3.post.Filter;
import com.jme3.post.FilterPostProcessor;
import com.jme3.post.HDRRenderer;
import com.jme3.post.filters.BloomFilter;
import com.jme3.post.filters.LightScatteringFilter;
import com.jme3.post.filters.RadialBlurFilter;
import com.jme3.renderer.queue.RenderQueue.ShadowMode;
import com.jme3.scene.CameraNode;
import com.jme3.scene.Geometry;
import com.jme3.scene.Node;
import com.jme3.scene.Spatial;
import com.jme3.scene.shape.Box;
import com.jme3.shadow.PssmShadowRenderer;
import com.jme3.water.WaterFilter;
import de.stoneageracer.vehicles.Car;

/**
 *
 * @author Basti
 */
public class RacingApplication extends SimpleApplication implements ActionListener{
    
    /**
     * Prefix before the actual kmh value
     */
    /**
     * Suffix after the acutal kmh value
     */
    public String kmhPrefix, kmhSuffix;
    /**
     * GUI Text
     */
    private BitmapText kmhText = null;
    /**
     * Camera following our Car
     */
    private ChaseCamera chaseCam;
    /**
     * Physics BulletAppState
     */
    public BitmapText debugInfo = null;
    /**
     * Physics Calculator
     */
    public BulletAppState bulletAppState;
    /**
     * waterPostProcessor
     * @deprecated 
     */
    private FilterPostProcessor waterProcessor = null;
    /**
     * Shadow PostProcessor
     */
    private PssmShadowRenderer pssmRenderer = null;
    /**
     * "Motion Blue" Filter
     * TODO: Rework the whole MotionBlur Filtering!!!!
     */
    private RadialBlurFilter rbf = null;
    /**
     * "Motion Blue" PostProcessor
     * TODO: Rework the whole MotionBlur Filtering!!!!
     */
    private FilterPostProcessor motionBlur = null;
    /**
     * Bloom PostProcessor
     * @deprecated 
     */
    private FilterPostProcessor bloom = null;
    /**
     * main Post Processing Filter which initialises most of the shaders
     */
    private FilterPostProcessor fpp = null;
    /**
     * LightScattering PostProcessor
     * @deprecated 
     */
    private FilterPostProcessor lightScatt = null;
    /**
     * HDR PostProcessor
     */
    private HDRRenderer hdrRender = null;
    /**
     * Direction of the MainLight
     * TODO: make lightDir changes affect Scene!!!!
     */
    private Vector3f lightDir = new Vector3f(.6f, -.3f, 1f);
    /**
     * the only player you can steer when automatically adding keys
     */
    private VehicleControl player = null;
    /**
     * the Node which your playerCar is attached to
     */
    private Node carNode;
    /**
     * the first Car created
     */
    private Car playerCar;
    /**
     * the sky Spatial
     * TODO: make the Sky moving
     */
    private Spatial sky=null;
    /**
     * Value to steer the first car (dont touch)
     */
    private float steeringValue = 0;
    /**
     * Value to accelerate the first car (dont touch)
     */
    private float accelerationValue = 0;
    /**
     * shadowQuality which is automatically set to the SettingsManagers gfx_shadow_quality
     */
    private int shadowQuality=0;
    /**
     * the automatically created AmbientLight
     */
    private AmbientLight ambLight=null;
    /**
     * the Main Sun light
     */
    private DirectionalLight light1=null;
    /**
     * the Second Sun light (differst in strength and direction)
     */
    private DirectionalLight light2=null;
    
    /**
     * Constructor sets some important settings
     */
    public RacingApplication(){
        setPauseOnLostFocus(false);
    }
    /**
     * Override this Method at the start of your App
     */
    @Override
    public void simpleInitApp() {
        if(!SettingsManager.showdebug)guiNode.detachAllChildren();
        else initDebug();
    }
    
    /**
     * Initialise Water at a height of height
     * @param height the Y-Value where the Water-surface begins
     * @deprecated 
     */
    public void initWater(float height){
        if(viewPort.getProcessors().contains(waterProcessor)){
            viewPort.removeProcessor(waterProcessor);
            viewPort.addProcessor(waterProcessor);
        }else{
            waterProcessor = new FilterPostProcessor(assetManager);
            WaterFilter water = new WaterFilter(rootNode, lightDir);
            water.setWaterHeight(height);
            waterProcessor.addFilter(water);
            viewPort.addProcessor(waterProcessor);
        }
    }
    
    /**
     * In case you need the Apps assetManager
     * @return your Apps assetManager
     */
    public AssetManager getAsset(){
        return assetManager;
    }
    
    /**
     * in case you need the RootNode
     * @return your Apps rootNode
     */
    public Node getRoot(){
        return rootNode;
    }
    
    /**
     * initialise Physics (BulletAppState)
     */
    public void initPhysics(){
        if(bulletAppState==null){
            bulletAppState = new BulletAppState();
            stateManager.attach(bulletAppState);
        }
    }
    
    /**
     * Add a Filter
     * @param filter such as Bloom, SSAO ...
     */
    public void addFilter(Filter filter){
        FilterPostProcessor processor = new FilterPostProcessor(assetManager);
        processor.addFilter(filter);
        viewPort.addProcessor(processor);
    }
    
    /**
     * adds a text to the GUI
     * @param text the String you want to be added
     * @param position the Position where to draw the text
     * @return returns the BitmapText so you can change it later
     */
    public BitmapText addGuiText(String text, Vector3f position){
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        BitmapText guiText = new BitmapText(guiFont, false);
        guiText.setSize(guiFont.getCharSet().getRenderedSize());
        guiText.setText(text);
        guiText.setLocalTranslation(position);
        guiNode.attachChild(guiText);
        return guiText;
    }
    
    /**
     * attachs a Debug Text to the guiNode
     */
    private void initDebug(){
        guiFont = assetManager.loadFont("Interface/Fonts/Console.fnt");
        debugInfo = new BitmapText(guiFont, false);
        debugInfo.setSize(guiFont.getCharSet().getRenderedSize());
        debugInfo.setText("");
        debugInfo.setLocalTranslation(10,400,0);
        guiNode.attachChild(debugInfo);
    }
    
    /**
     * Print KMH to your display
     * @param prefix 
     * @param suffix 
     * @param position the location where the GUY KMH Text is drawn
     */
    public void initKMH(String prefix, String suffix, Vector3f position){
        kmhPrefix = prefix;
        kmhSuffix = suffix;
        guiFont = assetManager.loadFont("Interface/Fonts/Default.fnt");
        kmhText = new BitmapText(guiFont, false);
        kmhText.setSize(guiFont.getCharSet().getRenderedSize());
        kmhText.setText(prefix+(int)Math.abs(player.getCurrentVehicleSpeedKmHour())+suffix);
        kmhText.setLocalTranslation(position);
        guiNode.attachChild(kmhText);
    }
    
    /**
     * initialises Bloom if its enabled in SettingsManager.gfx_bloom
     * @deprecated 
     */
    private void initBloom(){
        if(SettingsManager.gfx_bloom){
            if(viewPort.getProcessors().contains(bloom)){
                viewPort.removeProcessor(bloom);
                viewPort.addProcessor(bloom);
            }else{
                bloom = new FilterPostProcessor(assetManager);
                BloomFilter bf = new BloomFilter(BloomFilter.GlowMode.SceneAndObjects);
                bf.setBloomIntensity(2);
                bloom.addFilter(bf);
                viewPort.addProcessor(bloom);
            }
        }
    }
    
    /**
     * @deprecated 
     */
    public void initLightScattering(){
        if(lightScatt==null){
            Vector3f lightPos = lightDir.multLocal(-3000);
            lightScatt = new FilterPostProcessor(assetManager);
            LightScatteringFilter filter = new LightScatteringFilter(lightPos);
            lightScatt.addFilter(filter);
            viewPort.addProcessor(lightScatt);
        }
    }
    
    /**
     * initialises MotionBlur if its enabled in SettingsManager.gfx_bloom
     * TODO: Rework the whole MotionBlur Filtering!!!!
     */
    private void initMotionBlur(){
        if(SettingsManager.gfx_motionblur){
            if(viewPort.getProcessors().contains(motionBlur)){
                viewPort.removeProcessor(motionBlur);
                viewPort.addProcessor(motionBlur);
            }else{
                motionBlur = new FilterPostProcessor(assetManager);
                rbf = new RadialBlurFilter(1, 0);
                motionBlur.addFilter(rbf);
                viewPort.addProcessor(motionBlur);
            }
        }
    }
    
    public void initHDR(){
        if(hdrRender!=null){
            viewPort.removeProcessor(hdrRender);
            viewPort.addProcessor(hdrRender);
        }
        if(hdrRender==null&&SettingsManager.gfx_hdr){
            hdrRender = new HDRRenderer(assetManager, renderer);
            hdrRender.setSamples(0);
            hdrRender.setMaxIterations(20);
            hdrRender.setExposure(0.5f);
            hdrRender.setThrottle(0.33f);
            viewPort.addProcessor(hdrRender);
        }
    }
    
    /**
     * initialises PostProcessing Filters such as MotionBlur
     */
    public void initPostProcessing(){
       
        if(fpp!=null){
            fpp.removeAllFilters();
            if(viewPort.getProcessors().contains(fpp))viewPort.removeProcessor(fpp);
            fpp = null;
        }
        
        fpp = new FilterPostProcessor(assetManager);
        
        //WATER
        WaterFilter water = new WaterFilter(rootNode, lightDir);
        water.setWaterHeight(-30);
        fpp.addFilter(water);
            
        //LIGHTSCATTERING
        if(SettingsManager.gfx_lightscattering){
            Vector3f lightDir = new Vector3f(this.lightDir);
            Vector3f lightPos = lightDir.multLocal(-3000);
            LightScatteringFilter filter = new LightScatteringFilter(lightPos);
            fpp.addFilter(filter);
        }

        initHDR();
        
        //BLOOM
        if(SettingsManager.gfx_bloom){
            BloomFilter bf = new BloomFilter(BloomFilter.GlowMode.SceneAndObjects);
            bf.setBloomIntensity(5);
            fpp.addFilter(bf);
        }
        
        viewPort.addProcessor(fpp);
    }
    
    /**
     * initialises a Sphere-Sky with the given single Texture
     * @param textureUrl Url to where your sky-texture is saved
     * @return returns the sky-Spatial
     */
    public Spatial initSky(String textureUrl){
        sky = SkyFactory.createSky(assetManager, "Textures/Sky/Bright/FullskiesBlueClear03.dds", false);
        //sky = SkyFactory.createSky(assetManager, assetManager.loadTexture(textureUrl), new Vector3f(1,1,1), true);
        rootNode.attachChild(sky);
        return sky;
    }
    
    /**
     * initialises PssmShadowRenderer with correct lightDir
     */
    public void initShadow(){
        if(SettingsManager.gfx_shadows){
            if(viewPort.getProcessors().contains(pssmRenderer))viewPort.removeProcessor(pssmRenderer);
            pssmRenderer=null;
            SettingsManager.gfx_shadow_quality = SettingsManager.gfx_shadow_quality>8 ? 8 : SettingsManager.gfx_shadow_quality;
            SettingsManager.gfx_shadow_quality = SettingsManager.gfx_shadow_quality<1 ? 1 : SettingsManager.gfx_shadow_quality;
            pssmRenderer = new PssmShadowRenderer(assetManager, SettingsManager.gfx_shadow_quality*512,4,PssmShadowRenderer.EDGE_FILTERING_PCF);
            shadowQuality = SettingsManager.gfx_shadow_quality;
            //pssmRenderer = new PssmShadowRenderer(getAsset(), SettingsManager.gfx_shadow_quality*512,10);
            pssmRenderer.setDirection(lightDir);
            pssmRenderer.setLambda(.8f);
            pssmRenderer.setShadowIntensity(0.6f);
            //pssmRenderer.setPcfFilter(PssmShadowRenderer.FILTERING.PCF16X16);
            pssmRenderer.setEdgesThickness(3);
            viewPort.addProcessor(pssmRenderer);
        }
    }
    
    /**
     * initialises Key-Mapping "Left","Right","Up","Down","Reset","Brake"
     */
    public void initKeys(){
        
        inputManager.addMapping("Left", new KeyTrigger(KeyInput.KEY_LEFT));
        inputManager.addMapping("Right", new KeyTrigger(KeyInput.KEY_RIGHT));
        inputManager.addMapping("Up", new KeyTrigger(KeyInput.KEY_UP));
        inputManager.addMapping("Down", new KeyTrigger(KeyInput.KEY_DOWN));
        inputManager.addMapping("Reset", new KeyTrigger(KeyInput.KEY_RETURN));
        inputManager.addMapping("Brake", new KeyTrigger(KeyInput.KEY_SPACE));
        
        inputManager.addListener(this, "Left");
        inputManager.addListener(this, "Right");
        inputManager.addListener(this, "Up");
        inputManager.addListener(this, "Down");
        inputManager.addListener(this, "Reset");
        inputManager.addListener(this, "Brake");
    }
    
    /**
     * initialises 2x Directional Light
     * @param ambLight set true if you want an AmbientLight (LightGray)
     */
    private void initLight(boolean ambientLight){
        
        if(light1==null){
            light1 = new DirectionalLight();
            rootNode.addLight(light1);
        }
        light1.setDirection(lightDir.normalizeLocal());

        if(light2==null){
            light2 = new DirectionalLight();
            rootNode.addLight(light2);
        }
        light1.setDirection(new Vector3f(-lightDir.x, -lightDir.y/10, 0.3f).normalizeLocal());
        
        if(ambientLight){
            if(ambLight==null){
                ambLight = new AmbientLight();
                rootNode.addLight(ambLight);
            }
            ambLight.setColor(ColorRGBA.LightGray);
        }
    }
    
    /**
     * initialises your world including lights and shadows
     * @param scene the Model for the world
     * @param collisionShape the collisionModel for the world (use same as "scene" if you dont have a lower quality one
     * @return returns the Geometry of the world-model saved in "scene"
     */
    public Geometry initWorld(String scene, String collisionShape){
        
        initLight(true);
        
        initShadow();
        
        PhysicsSpace space = null;
        if(bulletAppState!=null)space = bulletAppState.getPhysicsSpace();
        
        Spatial floor = assetManager.loadModel(scene);
        Geometry floorGeometry = (Geometry)floor;
        floorGeometry.setShadowMode(ShadowMode.CastAndReceive);
        floorGeometry.scale(.05f);
       
        floorGeometry.setLocalTranslation(0, -20, 0);
        if(!(collisionShape==null)){
            Geometry collision = (Geometry)assetManager.loadModel(scene);
            collision.scale(.05f);
            floorGeometry.addControl(new RigidBodyControl(CollisionShapeFactory.createMeshShape(collision), 0));
        }
        rootNode.attachChild(floorGeometry);
        if(bulletAppState!=null)space.add(floorGeometry);
        
        return floorGeometry;
    }
    
    /**
     * checks whether Graphics changes are applied to the game already (only shadow so far)
     * TODO: checkSettings() only checks for shadow changes yet ... add bloom checking etc
     */
    private void checkSettings(){
        //checkShadows
        if(SettingsManager.gfx_shadows&&!viewPort.getProcessors().contains(pssmRenderer)){
            if(waterProcessor!=null)viewPort.removeProcessor(waterProcessor);
            if(pssmRenderer ==null)initShadow();
            else viewPort.addProcessor(pssmRenderer);
            if(waterProcessor!=null)viewPort.addProcessor(waterProcessor);
        }
        if(!SettingsManager.gfx_shadows&&viewPort.getProcessors().contains(pssmRenderer)){
            viewPort.removeProcessor(pssmRenderer);
        }
        if((pssmRenderer==null||shadowQuality!=SettingsManager.gfx_shadow_quality)&&SettingsManager.gfx_shadows){
            if(waterProcessor!=null)viewPort.removeProcessor(waterProcessor);
            initShadow();
            if(waterProcessor!=null)viewPort.addProcessor(waterProcessor);
        }
    }
    
    /**
     * initialises a ChaseCam following the targetNode
     * @param target Node the ChaseCam is following
     */
    public void initCam(Node target){
        chaseCam = new ChaseCamera(new CameraNode("Camera Node", cam).getCamera(), target, inputManager);
        chaseCam.setMaxDistance(40);
        chaseCam.setMinDistance(20);
        chaseCam.setDefaultDistance(15);
        chaseCam.setSmoothMotion(true);
        chaseCam.setTrailingEnabled(true);
        chaseCam.setTrailingSensitivity(10f);
    }
        
    /**
     * update executed while running
     * @param tpf "TimePerFrame" value. Use to keep game running smooth on different FPS
     */
    @Override
    public void simpleUpdate(float tpf) {
        //update with tpf (time per frame)
        if(rbf!=null&&player!=null&&bulletAppState!=null)rbf.setSampleStrength(Math.abs(player.getCurrentVehicleSpeedKmHour())/150);
        if(kmhText!=null)kmhText.setText(kmhPrefix+(int)Math.abs(player.getCurrentVehicleSpeedKmHour())+kmhSuffix);
        checkSettings();
        if(SettingsManager.showdebug){
            debugInfo.setText("version "+SettingsManager.version+"\n"
                    + "console "+SettingsManager.console+"\n"
                    + "motionblur "+SettingsManager.gfx_motionblur+"\n"
                    + "bloom "+SettingsManager.gfx_bloom+"\n"
                    + "shadows "+SettingsManager.gfx_shadows+"\n"
                    + "shadowQuality "+SettingsManager.gfx_shadow_quality+"\n"
                    + "hdr "+SettingsManager.gfx_hdr);
        }
    }
    
    /**
     * Creates a fire-like ParticleEmitter (needs to be added to a Node still!)
     * @param position local position
     * @param startColor starting color of the particles
     * @param endColor end color of the particles
     * @return returns an emitter (Geometry) to be added to any Node 
     */
    public ParticleEmitter createEmitter(Vector3f position, ColorRGBA startColor, ColorRGBA endColor){
        /** Uses Texture from jme3-test-data library! */
        ParticleEmitter fire = new ParticleEmitter("Emitter", ParticleMesh.Type.Triangle, 30);
        Material mat_red = new Material(assetManager, "Common/MatDefs/Misc/Particle.j3md");
        mat_red.setTexture("Texture", assetManager.loadTexture("Effects/Explosion/flame.png"));
        fire.setMaterial(mat_red);
        fire.setImagesX(2); fire.setImagesY(2); // 2x2 texture animation
        fire.setEndColor(endColor);   
        fire.setStartColor(startColor); 
        fire.setInitialVelocity(new Vector3f(0, 2, 0));
        fire.setStartSize(0.6f);
        fire.setEndSize(0.1f);
        fire.setGravity(0);
        fire.setLowLife(0.5f);
        fire.setHighLife(3f);
        fire.setVelocityVariation(0.3f);
        fire.setLocalTranslation(position);
        return fire;
    }
       
    /**
     * finds a special Geometry in a Spatial
     * @param spatial the Spatial to search in
     * @param name the name of the Geometry you want
     * @return the Geometry found with the given name (null if theres none)
     */
    private Geometry findGeom(Spatial spatial, String name) {
        if (spatial instanceof Node) {
            Node node = (Node) spatial;
            for (int i = 0; i < node.getQuantity(); i++) {
                Spatial child = node.getChild(i);
                Geometry result = findGeom(child, name);
                if (result != null) {
                    return result;
                }
            }
        } else if (spatial instanceof Geometry) {
            if (spatial.getName().startsWith(name)) {
                return (Geometry) spatial;
            }
        }
        return null;
    }
    
    /**
     * creates a Player - the first created Player is ALWAYS the one you steer (HIGHLY UNSTABLE VERSION)
     * @param obj url to an object
     * @param x x-spawn-coordinate
     * @param y y-spawn-coordinate
     * @param z z-spawn-coordinate
     * @return a Node including the playerObject
     * @deprecated
     */
    public Node buildPlayer(String obj, float x, float y, float z){
        float stiffness = 180.0f;//200=f1 car
        float compValue = 0.2f; //(lower than damp!)
        float dampValue = 0.3f;
        final float mass = 4000;


        //Load model and get chassis Geometry
        Spatial chasis = assetManager.loadModel(obj);
        chasis.rotate(0, FastMath.PI, 0);
        Node carNode = (Node)chasis;
        Geometry myBox = (Geometry) carNode.getChild(0);
        carNode.setShadowMode(ShadowMode.CastAndReceive);
        
        BoundingBox box = (BoundingBox) (myBox).getModelBound();
        
        //Using only the following collBox for collision instead of chasis increases FPS but results in unrealistic collisions
        Geometry collBox = new Geometry("collisionBox", new Box(box.getXExtent(),box.getYExtent(),box.getZExtent()));
        
        //Create a hull collision shape for the chassis
        CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(collBox);

        //Create a vehicle control
        VehicleControl myCar = new VehicleControl(carHull, mass);
        carNode.addControl(myCar);


        //Setting default values for wheels
        myCar.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness));
        myCar.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness));
        myCar.setSuspensionStiffness(stiffness);
        myCar.setMaxSuspensionForce(10000);

        
        if(player == null){
            player = myCar;
            this.playerCar = null;
        }
        
        myCar.setPhysicsLocation(new Vector3f(x,y,z));
        
        rootNode.attachChild(carNode);
        bulletAppState.getPhysicsSpace().add(myCar);
        return carNode;
    }
    
    /**
     * creates a Player - the first created Player is ALWAYS the one you steer
     * @param car Car object to add as a Player
     * @param x x-spawn-coordinate
     * @param y y-spawn-coordinate
     * @param z z-spawn-coordinate
     * @return a Node including the playerObject
     */
    public Car buildPlayer(Car car, float x, float y, float z) {
                
        float stiffness = 180.0f;//200=f1 car
        float compValue = 0.2f; //(lower than damp!)
        float dampValue = 0.3f;
        final float mass = 4000;

        //Load model and get chassis Geometry
        Node carNode = (Node)assetManager.loadModel(car.modelUrl);
        
        carNode.setShadowMode(ShadowMode.CastAndReceive);
        Geometry chasis = findGeom(carNode, car.carUrl);
        BoundingBox box = (BoundingBox) chasis.getModelBound();
        
        //Using only the following collBox for collision instead of chasis increases FPS but results in unrealistic collisions
        //Geometry collBox = new Geometry("collisionBox", new Box(box.getXExtent(),box.getYExtent(),box.getZExtent()));
        
        //Create a hull collision shape for the chassis
        CollisionShape carHull = CollisionShapeFactory.createDynamicMeshShape(chasis);

        //Create a vehicle control
        //VehicleControl myCar = new VehicleControl(carHull, mass);
        
        car.init(carHull,mass);
        
        carNode.addControl(car);


        //Setting default values for wheels
        car.setSuspensionCompression(compValue * 2.0f * FastMath.sqrt(stiffness));
        car.setSuspensionDamping(dampValue * 2.0f * FastMath.sqrt(stiffness));
        car.setSuspensionStiffness(stiffness);
        car.setMaxSuspensionForce(10000);


        //Create four wheels and add them at their locations
        //note that our fancy car actually goes backwards..
        Vector3f wheelDirection = new Vector3f(0, -1, 0);
        Vector3f wheelAxle = new Vector3f(-1, 0, 0);

        
        Geometry wheel_fr = findGeom(carNode, car.wheelFrontRight);
        wheel_fr.center();
        box = (BoundingBox) wheel_fr.getModelBound();
        float wheelRadius = box.getYExtent();
        float back_wheel_h = (wheelRadius * 1.7f) - 1f;
        float front_wheel_h = (wheelRadius * 1.9f) - 1f;
        car.addWheel(wheel_fr.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, true);


        Geometry wheel_fl = findGeom(carNode, car.wheelFrontLeft);
        wheel_fl.center();
        box = (BoundingBox) wheel_fl.getModelBound();
        car.addWheel(wheel_fl.getParent(), box.getCenter().add(0, -front_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, true);


        Geometry wheel_br = findGeom(carNode, car.wheelBackRight);
        wheel_br.center();
        box = (BoundingBox) wheel_br.getModelBound();
        car.addWheel(wheel_br.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, false);


        Geometry wheel_bl = findGeom(carNode, car.wheelBackLeft);
        wheel_bl.center();
        box = (BoundingBox) wheel_bl.getModelBound();
        car.addWheel(wheel_bl.getParent(), box.getCenter().add(0, -back_wheel_h, 0),
                wheelDirection, wheelAxle, 0.2f, wheelRadius, false);
        
        car.getWheel(2).setFrictionSlip(4);
        car.getWheel(3).setFrictionSlip(4);
        
        if(player == null){
            player = car;
        }
        
        car.setPhysicsLocation(new Vector3f(x,y,z));
        
        rootNode.attachChild(carNode);
        if(bulletAppState!=null)bulletAppState.getPhysicsSpace().add(car);
        car.node=carNode;
        return car;
    }

    
    /**
     * Resets the whole "level" by detaching all Nodes, cleaning physics handler and removing the player
     */
    public void reset(){
        guiNode.detachAllChildren();
        if(SettingsManager.showdebug){
            initDebug();
            guiNode.attachChild(statsView);
        }
        rootNode.detachAllChildren();
        bulletAppState.cleanup();
        stateManager.detach(bulletAppState);
        bulletAppState = null;
        player = null;
        
        if(bloom!=null)viewPort.removeProcessor(bloom);
        bloom = null;
        if(motionBlur!=null)viewPort.removeProcessor(motionBlur);
        motionBlur = null;
        if(pssmRenderer!=null)viewPort.removeProcessor(pssmRenderer);
        pssmRenderer = null;
        if(waterProcessor!=null)viewPort.removeProcessor(waterProcessor);
        waterProcessor = null;
    }
    
    /**
     * steering the car with the given onAction parameters
     * @param name name of the "key"
     * @param isPressed boolean whether a key is pressed
     * @param tpf "Time-Per-Frame"
     */
    private void steerCar(String name, boolean isPressed, float tpf){
        if (name.equals("Left")) {
            if (isPressed) {
                steeringValue += .2f;
            } else {
                steeringValue += -.2f;
            }
            player.steer(steeringValue);
        } else if (name.equals("Right")) {
            if (isPressed) {
                steeringValue += -.2f;
            } else {
                steeringValue += .2f;
            }
            player.steer(steeringValue);
        } //note that our fancy car actually goes backwards..
        else if (name.equals("Up")) {
            if (isPressed) {
                accelerationValue -= 8000;
            } else {
                accelerationValue += 8000;
            }
            player.accelerate(accelerationValue);
            //player.setCollisionShape(CollisionShapeFactory.createDynamicMeshShape(findGeom(carNode, playerCar.carUrl)));
        }else if (name.equals("Down")) {
            if (isPressed) {
                accelerationValue += 8000;
            } else {
                accelerationValue -= 8000;
            }
            player.accelerate(accelerationValue);
            //player.setCollisionShape(CollisionShapeFactory.createDynamicMeshShape(findGeom(carNode, playerCar.carUrl)));
        
        }else if (name.equals("Brake")) {
            if (isPressed) {
                player.brake(400f);
            } else {
                player.brake(0f);
            }
        } else if (name.equals("Reset")) {
            if (isPressed) {
                System.out.println("Reset");
                player.setPhysicsLocation(Vector3f.ZERO);
                player.setPhysicsRotation(new Matrix3f());
                player.setLinearVelocity(Vector3f.ZERO);
                player.setAngularVelocity(Vector3f.ZERO);
                player.resetSuspension();
            } else {
            }
        }
    }
    
    /**
     * ActionListener Method (Overrideable) executes steerCar([...]);
     * @param name name of the "key"
     * @param isPressed boolean whether a key is pressed
     * @param tpf "Time-Per-Frame"
     */
    public void onAction(String name, boolean isPressed, float tpf) {
        steerCar(name,isPressed,tpf);
    }
}
